package Stack;

# use Stack::SMC6724AL2;
use Stack::SMC6824M;
use Stack::SMC6248M;
use Stack::SMC8612XL3;
use Stack::CiscoCatalyst2950;
use Stack::DES3550;

use strict;
use lib '.';

use Utils;

use BER;
use SNMP_util;
require 'SNMP_Session.pm';

use Data::Dumper;
use Class::Rebless;

use base qw (NetworkDB);

use Net::Telnet;
use Logger;
use Switch;
use Plug;

__PACKAGE__->set_up_table('stacks');
__PACKAGE__->has_many(switches => 'Switch');
__PACKAGE__->has_a(switch_typeid => 'SwitchType');
__PACKAGE__->set_sql(segments => 'SELECT DISTINCT segmentid FROM plugs_export_view WHERE segmentid IS NOT NULL AND (is_vlan_trunk IS NULL OR is_vlan_trunk IS FALSE) AND stackip = ?');

my $conf = Conf->show;

# called from parent class
sub initialize {
	my $self = shift;
	my $swtype = $self->switch_typeid;		# objekt pre typ prepinaca
	if (defined $swtype && ref $swtype) {
		bless($self, __PACKAGE__."::".$swtype->class_name);	# urcenie typu switchu hw/sw
		$self->initialize;			# aby sa inicializovali data odvodenej triedy
	}
	$self->{'{MACswitchingDBloaded}'} = 0;				# asi deprecated
	$self->{'{ifOperStatusLoaded}'} = 0;
	$self->{telnet_connected} = 0;		# pocet sekund od zaciatku roka 1970, kedy doslo k telnetovemu pripojeniu k stohu z tohto objektu (ak objekt od svojho vzniku sa este netelnetol, tak tam ma "0")
}

sub segments {
	my $self = shift;
	my $sth = $self->sql_segments;
	$sth->execute($self->ip);
	return Segment->sth_to_objects($sth);
}

sub cmd {
	my $self = shift;
	my ($commands) = @_;
	Logger->write(__PACKAGE__." $commands");
	$self->{telnet_connected} = time;
	$self->{CLI} = new Net::Telnet (
		Timeout => 20,
		Errmode => 'return',
		Prompt => '/'.$self->CLI_prompt.'$/i'
	);
	my $CLI_connect_result = $self->{CLI}->open($self->ip);
        if ($CLI_connect_result eq 'ERROR') {
		Logger->write(__PACKAGE__." ERROR: ".$CLI_connect_result);
        } else {
       		$self->{CLI}->login($conf->{MGMT_USER}, $conf->{MGMT_PASSWORD});
		my $MGMT_ENABLE_PWD = $conf->{MGMT_ENABLE_PWD};				# docasne, kvoli D-Linkom, ktore zatial nevedia akceptovat
											# implicitne nastavenie admin opravneni po prihlaseni cez RADIUS
		$self->{CLI}->cmd("enable admin\n$MGMT_ENABLE_PWD\n");
        # enable admin len docasne - v release5 firmware DES-3550 ma RADIUS klient akceptovat Service-Type
        # Administrative-User, takze potom bude user 'lanmgmt' automaticky v enable mode

	}
#	return $commands."\n".$self->{CLI}->cmd($commands);
#	$commands.$self->{CLI}->cmd($commands);
	return ($commands),$self->{CLI}->cmd($commands);
#	$self->{CLI}->waitfor('/\-\-\-More\-\-\-/');
#	$self->{CLI}->print(" ");
}

sub ifOperStatusPorts {		# porty, ktorych hodnota ifOperStatus 1.3.6.1.2.1.2.2.1.8 je ...
	my $self = shift;
	my ($status) = @_;	# state number (look mib-2 for details)
	return @{$self->ifOperStates->{state2ports}->{$status}};
}

sub ifOperStates {
	my $self = shift;
	my $portstate_struct;
	if ((time - $self->{'{ifOperStatusLoaded}'}) > 300) {
#		warn "Reading ifOperStatus of the ports of the stack "
#			.$self->stackid." (".$self->room_name.") . . .\n"; my $time_before = time;
		my @ifOperStates = $self->SNMPwalk('1.3.6.1.2.1.2.2.1.8');
#		warn "Done. Operation has taken ".(time - $time_before)." seconds.\n";
		foreach my $portstate (@ifOperStates) {
			my ($portnum,$state) = split(':', $portstate);
			$portstate_struct->{port2state}->{$portnum} = $state;
			push @{$portstate_struct->{state2ports}->{$state}}, $portnum;
		}
		$self->{'{ifOperStatus}'} = $portstate_struct;
		$self->{'{ifOperStatusLoaded}'} = time;	# aby sa to loadovalo iba raz, a to vtedy ked treba
	}
	return $self->{'{ifOperStatus}'};
}


sub MACdb {
	my $self = shift;
	if ((time - $self->{'{MACswitchingDBloaded}'}) > 300) {
		$self->{'{MACswitchingDB}'} = $self->SNMPgetMAClist;
		$self->{'{MACswitchingDBloaded}'} = time;	# zabezpeci, aby sa MACdb nacitala zo switchu len raz
	}
	return $self->{'{MACswitchingDB}'};
}

sub dbMAC2port {
	my $self = shift;
	my ($MAC) = @_;
	return $self->MACdb->{MAC2port}->{$MAC};
}

sub DBport2MAC {
	my $self = shift;
	my ($port) = @_;
	return $self->MACdb->{port2MAC}->{$port};
}

sub portsWithMAC {
	my $self = shift;
	return (keys %{$self->MACdb->{port2MAC}});
}


sub userPorts {
	my $self = shift;
	my $userPorts = {};
	foreach my $activePort ($self->portsWithMAC) {
		$userPorts->{$activePort} = $self->DBport2MAC($activePort);
	}
	return $userPorts;
}

sub getMasterSwitch {
        my $self = shift;
        return ($self->getUnit(0) || $self->getUnit(1));
}


sub getUnit {			# na zaklade cisla typicky v rozmedzi 0 - 8 vrati cislo unitu
	my $self = shift;
	my $unit_no = shift;
	foreach ($self->switches) {
		if ($_->unit == $unit_no) { return $_; }
	}	

}

sub GetMAClist {
# vstup: port, vlan_id
# vystup: pole MAC adries zapamatanych na danom porte
# (a tym padom aj na prislusnej VLANe, kedze kazdy ne-trunkovy port ma nastavenu prave jednu nativnu VLAN)
	my $self = shift;
	my ($portnum, $vlan_id) = @_;
	my @MAClist;
	my $time_before = time;
	Logger->write(__PACKAGE__." Reading MAC adddresses from ".$self->hostname."...");
	my @macport = $self->SNMPgetMAClist($vlan_id);	# switch_type specificke
	my $time_after = time; my $period = $time_after - $time_before;
	Logger->write(__PACKAGE__."Selecting MAC addresses only on the port $portnum of ".$self->hostname.". . .");
        foreach (@macport) {
                my @macport_pair = split(/\:/,$_);
		my @MACdec = split /\./, $macport_pair[0];
		#vyparsovanie MAC z vystupu SNMPwalku:
		@MACdec = ($MACdec[-6], $MACdec[-5], $MACdec[-4], $MACdec[-3], $MACdec[-2], $MACdec[-1]);
		my $MACdec = join '.', @MACdec;
		my $MAC = Utils::MACdec2hex($MACdec);
		my $actualportnum = $macport_pair[1];
		if ($portnum == $macport_pair[1]) {
			push @MAClist, $MAC;
		}
        }
	return @MAClist;
}

sub SNMPwalk {
	my $self = shift;
        my ($oid, $vlan_id) = @_;	# vlan_id pridal 3.8. 2006 ostry kvoli Cisco 2950, ktoremu treba povedat aj cislo VLAN, ked chceme odcitavat porty cez SNMP
	my $community = $conf->{MGMT_COMMUNITY};
	if (defined $vlan_id) {
		$community .= '@'.$vlan_id;
	}
	my $ip = $self->ip;
	my $command = "snmpwalk -Os -v 2c -c $community $ip $oid 2> /dev/null";
	my @SNMPoutput = `$command`; my @SNMPoutputCOLON;
	foreach my $line (@SNMPoutput) {
		chomp $line;
		my @line = split / /,$line;
		push @SNMPoutputCOLON, join(':', @line);
	}
	return @SNMPoutputCOLON;
}

sub SNMPget {
	my $self = shift;
	my ($oid, $vlan_id) = @_;	# vlan_id pridal 3.8. 2006 ostry kvoli Cisco 2950, ktoremu treba povedat aj cislo VLAN, ked chceme odcitavat porty cez SNMP
	my $community = $conf->{MGMT_COMMUNITY};
	if (defined $vlan_id) {
		$community .= '@'.$vlan_id;
	}
	my $ip = $self->ip;
	my $snmpcommand = "snmpget -Os -v 2c -c $community $ip $oid 2> /dev/null";
	my $SNMPoutput = `$snmpcommand`;
	my @SNMPoutput = split / /, $SNMPoutput;
	if ($#SNMPoutput >= 0) {
		chomp $SNMPoutput[-1];
	}
	return $SNMPoutput[-1];	# vrati posledny prvok dvojice variable/value (cize value ;)
#        return snmpget($community."\@".$self->ip, $oid);
}

sub MAC2Plug {
        my $self = shift;
        my ($MAC, $vlan_id) = @_;	# vlan_id pridal 3.8. 2006 ostry kvoli Cisco 2950, ktoremu treba povedat aj cislo VLAN, ked chceme odcitavat porty cez SNMP
        my $MACdec = Utils::MAChex2dec($MAC);
	Logger->write(__PACKAGE__." Searching $MAC on ports of ".$self->hostname." in progress...");
        my $portnum = $self->MAC2Port($MACdec, $vlan_id);
	Logger->write(__PACKAGE__." Searching $MAC on ports of ".$self->hostname." done.");	
	if (defined $portnum && $portnum && $portnum =~ /^[0-9]+$/) {
		Logger->write(__PACKAGE__." $portnum OK.");	
	} else {
		$portnum = 0;
		Logger->write(__PACKAGE__." $portnum NOT OK.");	
	}
	Logger->write(__PACKAGE__." Port2Plug in progress...");	
	my $plug = $self->Port2Plug($portnum);
	Logger->write(__PACKAGE__." Port2Plug done.");	
	return $plug; 
}

sub Port2Plug { # pre dany stack zisti z cisla portu, ktory je to unit a zavola nan Switch::Port2Plug()
	my $self = shift;
	my ($portnum) = @_;	# cislo v ramci stohu
	my ($unit, $port) = $self->translatePortNumber($portnum);	# poradove cislo switchu a portu v ramci switchu
        my $is_elsewhere = 0;
        my $switch = $self->getUnit($unit);	# vytvori pre por. cislo switchu dotycny objekt Switch
	if (!$switch) {
		Logger->write(__PACKAGE__." NON-USER PORT: $portnum");
		return $is_elsewhere;
	}
        return $switch->Port2Plug($port);
}

sub saveconfig {
	my $self = shift;
	return $self->cmd($self->CLI_saveconfig);
}

sub standard_conf_filename {
	my $self = shift;
	return $self->hostname."_config";
}

sub configure_standard_global_params {	# standardne nastavi (S)NTP, syslog, RADIUS, TACACS+, SNMP ...
	my $self = shift;
	$self->cmd($self->CLI_configure.$self->CLI_set_TACACS($conf->{TACACS_KEY},$conf->{TACACS_SERVER}).
		$self->CLI_set_RADIUS($conf->{RADIUS_KEY}, $conf->{RADIUS_SERVER}).
		$self->CLI_set_GVRP(0).
		$self->CLI_set_SNTP($conf->{NTP_SERVER}).
		$self->CLI_set_ident($self->hostname, $self->room_name, $self->admin_contact).
		$self->CLI_set_SNMPcommunity($conf->{MGMT_COMMUNITY}, 1).
		$self->CLI_set_syslog($conf->{SYSLOG_HOST}, 0).
		$self->CLI_set_logging($conf->{SYSLOG_FACILITY}));
 
}

# zakomentoval ostry 25.4.2007 z testovacich dovodov
#sub DESTROY {	# destruktor - musi sa volat takto!
#	my $self = shift;
#	if (ref $self && $self->{telnet_connected}) {
#		$self->{CLI}->cmd($self->CLI_quit);
#	}
#}

1;
